#include "s21_matrix.h"

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

// Rule #1: always set pointers to NULL after freeing it

/*
Аллокация, устойчивая к отказу malloc*/
int s21_create_matrix(int rows, int columns, matrix_t *result) {
  if (rows <= 0 || columns <= 0) {
    return INCORRECT_MATRIX;
  }
  // get resources
  int is_ok = 1;
  result->rows = rows;
  result->columns = columns;
  result->matrix = malloc(rows * sizeof(double *));
  if (result->matrix) {
    for (int row = 0; row < rows && is_ok; row++) {
      result->matrix[row] = malloc(columns * sizeof(double));
      if (!result->matrix[row]) is_ok = 0;
    }
    if (!is_ok) {
      s21_remove_matrix(result);
    }
  } else {
    is_ok = 0;
  }
  return (is_ok) ? OK : CALCULATION_ERROR;
}

/*
Удаляет содержимое, если оно не равно NULL*/
void s21_remove_matrix(matrix_t *A) {
  if (A->matrix) {
    for (int row = 0; row < A->rows; row++) {
      if (A->matrix[row]) free(A->matrix[row]);
    }
    free(A->matrix);
    A->matrix = NULL;
  }
  A->rows = 0;
  A->columns = 0;
}

/*
Проверяет каждую ячейку*/
int s21_eq_matrix(matrix_t *A, matrix_t *B) {
  int is_eq = (A->rows == B->rows && A->columns == B->columns);
  for (int row = 0; row < A->rows && is_eq; row++) {
    for (int column = 0; column < A->columns && is_eq; column++) {
      if (fabs(A->matrix[row][column] - B->matrix[row][column]) > 1E-7)
        is_eq = 0;
    }
  }
  return is_eq ? SUCCESS : FAILURE;
}

int s21_check_matrix(matrix_t *A) {
  int is_ok = 1;
  if (A->rows <= 0 || A->columns <= 0) is_ok = 0;
  if (!is_ok || !A->matrix) is_ok = 0;
  for (int row = 0; row < A->rows && is_ok; row++) {
    if (!A->matrix[row]) is_ok = 0;
  }
  return (is_ok) ? OK : INCORRECT_MATRIX;
}

int s21_sum_matrix(matrix_t *A, matrix_t *B, matrix_t *result) {
  if (s21_check_matrix(A) == INCORRECT_MATRIX ||
      s21_check_matrix(B) == INCORRECT_MATRIX)
    return INCORRECT_MATRIX;
  if (A->rows != B->rows || A->columns != B->columns) return CALCULATION_ERROR;
  int responce = s21_create_matrix(A->rows, A->columns, result);
  if (responce != OK) return responce;

  for (int row = 0; row < A->rows; row++) {
    for (int column = 0; column < A->columns; column++) {
      result->matrix[row][column] =
          A->matrix[row][column] + B->matrix[row][column];
    }
  }
  return OK;
}

int s21_sub_matrix(matrix_t *A, matrix_t *B, matrix_t *result) {
  if (s21_check_matrix(A) == INCORRECT_MATRIX ||
      s21_check_matrix(B) == INCORRECT_MATRIX)
    return INCORRECT_MATRIX;
  if (A->rows != B->rows || A->columns != B->columns) return CALCULATION_ERROR;
  int responce = s21_create_matrix(A->rows, A->columns, result);
  if (responce != OK) return responce;

  for (int row = 0; row < A->rows; row++) {
    for (int column = 0; column < A->columns; column++) {
      result->matrix[row][column] =
          A->matrix[row][column] - B->matrix[row][column];
    }
  }
  return OK;
}

int s21_mult_number(matrix_t *A, double number, matrix_t *result) {
  if (s21_check_matrix(A) == INCORRECT_MATRIX) return INCORRECT_MATRIX;
  int responce = s21_create_matrix(A->rows, A->columns, result);
  if (responce != OK) return responce;

  for (int row = 0; row < A->rows; row++) {
    for (int column = 0; column < A->columns; column++) {
      result->matrix[row][column] = number * A->matrix[row][column];
    }
  }
  return OK;
}

int s21_mult_matrix(matrix_t *A, matrix_t *B, matrix_t *result) {
  if (s21_check_matrix(A) == INCORRECT_MATRIX ||
      s21_check_matrix(B) == INCORRECT_MATRIX)
    return INCORRECT_MATRIX;
  if (A->columns != B->rows) return CALCULATION_ERROR;
  int responce = s21_create_matrix(A->rows, B->columns, result);
  if (responce != OK) return responce;
  for (int row = 0; row < A->rows; row++) {
    for (int column = 0; column < B->columns; column++) {
      result->matrix[row][column] = 0;
      for (int i = 0; i < A->columns; i++) {
        result->matrix[row][column] += A->matrix[row][i] * B->matrix[i][column];
      }
    }
  }
  return OK;
}

int s21_transpose(matrix_t *A, matrix_t *result) {
  if (s21_check_matrix(A) == INCORRECT_MATRIX) return INCORRECT_MATRIX;
  int responce = s21_create_matrix(A->columns, A->rows, result);
  if (responce != OK) return responce;

  for (int row = 0; row < A->rows; row++) {
    for (int column = 0; column < A->columns; column++) {
      result->matrix[column][row] = A->matrix[row][column];
    }
  }
  return OK;
}

/*
Рекурсивно вычесляет детерминант
Проверяет ввод на соответствие сторон
Ресурсы внутри условного деления освобождает там же*/
int s21_determinant(matrix_t *A, double *result) {
  if (s21_check_matrix(A) == INCORRECT_MATRIX) return INCORRECT_MATRIX;
  if (A->rows != A->columns) return CALCULATION_ERROR;
  if (A->rows > 1) {
    matrix_t tmp_matrix;
    double tmp_result;
    int responce = s21_create_matrix(A->rows - 1, A->columns - 1, &tmp_matrix);
    if (responce != OK) return responce;
    *result = 0;
    for (int i = 0; i < A->rows; i++) {
      for (int row = 0; row < A->rows; row++) {
        if (row == i) continue;
        for (int column = 1; column < A->columns; column++) {
          tmp_matrix.matrix[(i < row) ? row - 1 : row][column - 1] =
              A->matrix[row][column];
        }
      }
      responce = s21_determinant(&tmp_matrix, &tmp_result);
      if (responce != OK) {
        s21_remove_matrix(&tmp_matrix);
        return CALCULATION_ERROR;
      }
      *result += A->matrix[i][0] * pow(-1, i) * tmp_result;
    }
    s21_remove_matrix(&tmp_matrix);
  } else if (A->rows == 1) {
    *result = A->matrix[0][0];
  }
  return OK;
}

/*
Вычесляет миноры с помощью s21_determinant
Проверяет ввод на соответствие сторон
Ресурсы внутри условного деления освобождает там же*/
int s21_calc_complements(matrix_t *A, matrix_t *result) {
  // check input
  if (s21_check_matrix(A) == INCORRECT_MATRIX) return INCORRECT_MATRIX;
  if (A->rows != A->columns) return CALCULATION_ERROR;
  int responce = s21_create_matrix(A->rows, A->columns, result);
  if (responce != OK) return responce;
  int status = OK;
  if (A->rows == 1) {
    result->matrix[0][0] = 1;
  } else {
    matrix_t tmp_matrix;
    double tmp_det_result;
    responce = s21_create_matrix(A->rows - 1, A->columns - 1, &tmp_matrix);
    if (responce != OK) status = responce;
    for (int row = 0; row < A->rows && status == OK; row++) {
      for (int column = 0; column < A->columns && status == OK; column++) {
        //Создаю матрицу с исключенными столбцом и строчкой. Лучше так, чем
        //отдельной функцией
        for (int i = 0; i < A->rows; i++) {
          for (int j = 0; j < A->columns; j++) {
            if (i == row || j == column) continue;
            tmp_matrix.matrix[(i > row) ? i - 1 : i][(j > column) ? j - 1 : j] =
                A->matrix[i][j];
          }
        }
        responce = s21_determinant(&tmp_matrix, &tmp_det_result);
        if (responce != OK) status = responce;
        result->matrix[row][column] = pow(-1, row + column) * tmp_det_result;
      }
    }
    s21_remove_matrix(&tmp_matrix);
  }
  if (status != OK) s21_remove_matrix(result);
  return status;
}

int s21_inverse_matrix(matrix_t *A, matrix_t *result) {
  if (s21_check_matrix(A) == INCORRECT_MATRIX) return INCORRECT_MATRIX;
  if (A->rows != A->columns) return CALCULATION_ERROR;
  int status = OK;
  double determinant;
  int responce = s21_determinant(A, &determinant);
  if (responce != OK) status = responce;
  if (fabs(determinant) < 1E-8) status = CALCULATION_ERROR;

  matrix_t tmp_matrix, tmp_matrixT;

  responce = s21_calc_complements(A, &tmp_matrix);
  if (responce != OK) status = responce;

  responce = s21_transpose(&tmp_matrix, &tmp_matrixT);
  if (responce != OK) status = responce;

  if (status == OK) {
    responce = s21_mult_number(&tmp_matrixT, 1.0 / determinant, result);
    if (responce != OK) status = responce;
  }

  s21_remove_matrix(&tmp_matrix);
  s21_remove_matrix(&tmp_matrixT);
  return status;
}